//-----------------------------------------------------------------------------
//
// FifteenPuzzle
//
// Eugene Magdel and Joshua Jung
//
// 2011 for University of Illinois at Chicago CS340 w/ Professor Troy
//
//-----------------------------------------------------------------------------
#include "grid.h"
#include "time.h"

//-----------------------------------------------------------------------------
//
// Constructor
//
//-----------------------------------------------------------------------------
Grid::Grid(int _rows_count) : rows_count(_rows_count){
    srand(time(NULL));
    first = NULL;
    current = NULL;
    
    for(int i=0; i<size(); i++){
        push_back(new Block(i+1, i + 1 == size(), this));
    }

    mix();
}

//-----------------------------------------------------------------------------
//
// Methods
//
//-----------------------------------------------------------------------------
/**
 * Debugging: used to print out the position of all the blocks.
 */
void Grid::print(){ 
    int i = 0;
    for(Block * block = first; block != NULL; block = block -> next){
        i++;
        cout << "Value is: " << block -> get_value() << endl;
    }
}

/**
 * Returns the number of rows.
 */
int Grid::rows(){
    return rows_count;
}

/**
 * Returns true if the puzzle is solved.
 */
bool Grid::solved(){
    for( Block * block = first; block -> next != NULL; block = block -> next){
        if(block -> get_value() != block -> next -> get_value() - 1){
           return false; 
        }
    }
    return true;
}

/**
 * Returns true if puzzle is solvable (e.g. number of inversions are even)
 */
bool Grid::solvable(){
    int inversion_count = 0;
    for(Block * outer_block = first; outer_block != NULL; outer_block = outer_block -> next){
        for(Block * inner_block = outer_block; inner_block != NULL; inner_block = inner_block -> next){
            if(outer_block -> is_empty() || inner_block -> is_empty())
                continue;
            else if(outer_block -> get_value() > inner_block -> get_value())
                ++inversion_count;
        }
    }
    return inversion_count % 2 == 0;
}

/**
 * Returns the size of the puzzle. Normally 16.
 */
int Grid::size(){
   return rows() * rows(); 
}

/**
 * Returns the block at the specified index.
 */
Block& Grid::block_at(int index){
    return *(forward_pointer(first, index));
}

/**
 * This continually shuffles the board until a combination of tiles is found that is not
 * the solution and is also solvable.
 */
void Grid::mix(){
    while(solved() || !solvable()){
        for(int i=0; i<size(); i++){
            int position = rand() % size();
            swap(forward_pointer(first, i), forward_pointer(first, position));
        }
    }
}

/**
 * Sorts the grid into the correct order.
 */
void Grid::solve(){
    sort();
}

/**
 * Moves the provided block into the empty position if it is adjacent.
 */
void Grid::move(Block *block){
    int index = 0;

    Block * given_block = NULL;

    for(given_block = first; given_block != block; given_block = given_block -> next){
        ++index;
    }

    for(int dir=RIGHT; dir<=UP; dir++){
      switch(dir){
        case RIGHT:
            if(!right_row(index) && block_at(index+1).is_empty())
              swap(block, &block_at(index+1));
            break;
        case DOWN:
            if(!bottom_row(index) && block_at(index+rows()).is_empty())
              swap(block, &block_at(index+rows()));
            break;
        case LEFT:
            if(!left_row(index) && block_at(index-1).is_empty())
              swap(block, &block_at(index-1));
            break;
        case UP:
            std::cout << "GO UP" << endl;
            if(!top_row(index) && block_at(index-rows()).is_empty()){
                swap(block, &block_at(index-rows()));
            }
            break;
      }
    }
}

/**
 * Swaps the two provided blocks in the list.
 */
void Grid::swap(Block * given, Block * with){
    if(given == with)
        return;
    int given_val = given -> get_value();
    bool given_empty = given -> empty;
    QPixmap *given_pm = given -> pm;
    QGraphicsPixmapItem * given_pmi = given -> pmi;
    given -> value = with -> get_value();
    given -> empty = with -> empty;
    given -> pm = with -> pm;
    given -> pmi = with -> pmi;
    with -> value = given_val;
    with -> empty = given_empty;
    with -> pm = given_pm;
    with -> pmi = given_pmi;
}

/**
 * Returns true if the index is in the top row or beyond.
 */
bool Grid::top_row(int index){
  return index < rows(); 
}

/**
 * Returns true if the index is in the bottom row or greater.
 */
bool Grid::bottom_row(int index){
  return index >= size() - rows();
}

/**
 * Returns true if the index is in the left "column".
 */
bool Grid::left_row(int index){
  return index % rows() == 4;
}

/**
 * Returns true if the index is in the right "column".
 */
bool Grid::right_row(int index){
  return index % rows() == 3;
}

/**
 * Returns the block "amount" positions to the right of the provided block in the list.
 */
Block * Grid::forward_pointer(Block *pointer, int amount){
  if(amount>0){
    for(int i=0; i<amount; i++){
       pointer = pointer -> next;
    }
  }
  else if(amount < 0){
    for(int i=amount; i>0; i--)
        pointer = pointer -> prev;
  }
  return pointer;
}

/**
 * Pushes a block onto the end of the list.
 */
void Grid::push_back(Block* block){
    if(first == NULL){
        first = block;
        first -> next = NULL;
        first -> prev = NULL;
    }
    else{
        current -> next = block;
        block -> prev = current;
    }
    current = block;
}

/**
 * Sorts all blocks into the correct order.
 */
void Grid::sort(){
    bool order_broke = false;
    while(!order_broke){
        order_broke = true;
        for(Block * block = first; block -> next != NULL; block = block -> next){
            if(((*block) > (block -> next))){
                swap(block, block -> next);
                order_broke = false;
                break;
            }
        }
    }
}

/**
 * Initializes all the blocks / tiles.
 */
void Grid::initialize_tiles(QGraphicsScene *graphicsScene){
    for(Block * block = first; block != NULL; block = block -> next){
        block -> initialize_tile(graphicsScene);
    }
}

/**
 * Loops through and draws all the tiles / blocks.
 */
void Grid::draw_tiles(){

    bool isSolved = solved();

    for(Block * block = first; block != NULL; block = block -> next){
        block -> draw_tile(isSolved);
    }
}

/**
 * Handles a mouse click.
 */
void Grid::click(int x, int y){
    x -= 18;
    y -= 58;

    x /= 50;
    y /= 50;

    if (x < 0 || y < 0 || x > 3 || y > 3)
    {
        return;
    }

    int i = y * rows() + x;
    block_at(i).move();
    draw_tiles();
}
